/**
 * \file sse_type.hpp
 * 
 * \section COPYRIGHT
 *
 * Basis: A 3D Mathematics Library
 *
 * ---------------------------------------------------------------------
 *
 * Copyright (c) 2010, Don Olmstead
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *  3. Neither the name of organization nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef BASIS_DETAIL_SSE_TYPE_HPP_INCLUDED
#define BASIS_DETAIL_SSE_TYPE_HPP_INCLUDED

#include <basis/detail/config.hpp>

// Check if SSE instructions are available
#if (BASIS_SSE_LEVEL > 0)

namespace basis { namespace detail
{
	/**
	 * A wrapper around an SSE datatype.
	 */
	template <typename Real>
	class sse_type;

	/**
	 * A wrapper around a floating-point SSE datatype.
	 *
	 * \author Don Olmstead
	 * \version 0.1
	 */
	template <>
	class sse_type<float>
	{
		public:

			//------------------------------------------------------------
			// Member types
			//------------------------------------------------------------

			/// A real number type
			typedef float value_type;
			/// Reference to a real number type
			typedef float& reference;
			/// Unsigned integral type
			typedef std::size_t size_type;
			/// Mask containing the results of a comparison
			typedef int comparison_mask;

			//------------------------------------------------------------
			// Construction/Destruction
			//------------------------------------------------------------

			/**
			 * Creates an instance of the sse_type class.
			 *
			 * Left uninitialized to speed construction.
			 */
			inline sse_type()
			{ }

			/**
			 * Initializes an instance of the sse_type class.
			 *
			 * \param value1 The 1st component.
			 * \param value2 The 2nd component.
			 * \param value3 The 3rd component.
			 * \param value4 The 4th component.
			 */
			inline sse_type(value_type value1, value_type value2, value_type value3, value_type value4)
				: _values(_mm_set_ps(value4, value3, value2, value1))
			{ }

			/**
			 * Initializes an instance of the sse_type class.
			 *
			 * \param value The value to assign to all the components.
			 */
			inline sse_type(value_type value)
				: _values(_mm_set_ps1(value))
			{ }

			/**
			 * Initializes an instance of the sse_type class.
			 *
			 * \param values An array containing the values for the components.
			 */
			inline sse_type(const value_type* values)
				: _values(_mm_loadu_ps(values))
			{ }

			/**
			 * Initializes an instance of the sse_type class.
			 *
			 * \param copy The instance to copy.
			 */
			inline sse_type(const sse_type& copy)
				: _values(copy._values)
			{ }

			//------------------------------------------------------------
			// Assignment
			//------------------------------------------------------------
			
			/**
			 * Assignment operator for the sse_type class.
			 *
			 * \param copy The instance to copy.
			 */
			inline sse_type& operator= (const sse_type& copy)
			{
				_values = copy._values;

				return *this;
			}

			//------------------------------------------------------------
			// Element access
			//------------------------------------------------------------

			/**
			 * Element access for the sse_type.
			 *
			 * \param i The index of the element.
			 * \returns The value of the element.
			 */
			inline value_type operator[] (size_type i) const
			{
				BASIS_ASSERT(i < 4, "Index out of range!");

				return _values.m128_f32[i];
			}

			/**
			 * Element access for the sse_type.
			 *
			 * \param i The index of the element.
			 * \returns The value of the element.
			 */
			inline reference operator[] (size_type i)
			{
				BASIS_ASSERT(i < 4, "Index out of range!");

				return _values.m128_f32[i];
			}

			/**
			 * Sets all the values of the sse_type.
			 *
			 * \param value1 The 1st component.
			 * \param value2 The 2nd component.
			 * \param value3 The 3rd component.
			 * \param value4 The 4th component.
			 */
			inline void set_values(value_type value1, value_type value2, value_type value3, value_type value4)
			{
				_values = _mm_set_ps(value4, value3, value2, value1);
			}

			/**
			 * Accessor for the size of the sse_type.
			 *
			 * Size is the number of elements that can be
			 * accessed within the type.
			 *
			 * \returns The size of the sse_type.
			 */
			inline static size_type size()
			{
				return 4;
			}

			/**
			 * Accessor for the capacity of the sse_type.
			 *
			 * Capacity is the number of elements that could be
			 * stored in the type.
			 *
			 * \returns The capacity of the sse_type.
			 */
			inline static size_type capacity()
			{
				return 4;
			}

			//------------------------------------------------------------
			// Comparison operations
			//------------------------------------------------------------

			/// Flags for comparison results
			enum comparison_flags
			{
				/// Results of the comparison at the 1st element
				true_0 = 0x01,
				/// Results of the comparison at the 2nd element
				true_1 = 0x02,
				/// Results of the comparison at the 3rd element
				true_2 = 0x04,
				/// Results of the comparison at the 4th element
				true_3 = 0x08
			} ;

			/**
			 * Returns a comparison flag containing all values.
			 *
			 * \returns A comparison flag containing all values.
			 */
			inline static comparison_flags mask_all()
			{
				return (comparison_flags)(true_0 | true_1 | true_2 | true_3);
			}

			/**
			 * Compares the components of two sse_types for equality.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask equal(const sse_type& lhs, const sse_type& rhs)
			{
				return _mm_movemask_ps(_mm_cmpeq_ps(lhs._values, rhs._values));
			}

			/**
			 * Compares the components of two sse_types for inequality.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask not_equal(const sse_type& lhs, const sse_type& rhs)
			{
				return _mm_movemask_ps(_mm_cmpneq_ps(lhs._values, rhs._values));
			}

			/**
			 * Performs a less than comparison on the components of two sse_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask less_than(const sse_type& lhs, const sse_type& rhs)
			{
				return _mm_movemask_ps(_mm_cmplt_ps(lhs._values, rhs._values));
			}

			/**
			 * Performs a less than or equal to comparison on the components of two sse_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask less_than_equal(const sse_type& lhs, const sse_type& rhs)
			{
				return _mm_movemask_ps(_mm_cmple_ps(lhs._values, rhs._values));
			}

			/**
			 * Performs a greater than comparison on the components of two sse_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask greater_than(const sse_type& lhs, const sse_type& rhs)
			{
				return _mm_movemask_ps(_mm_cmpgt_ps(lhs._values, rhs._values));
			}

			/**
			 * Performs a greater than or equal to comparison on the components of two sse_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask greater_than_equal(const sse_type& lhs, const sse_type& rhs)
			{
				return _mm_movemask_ps(_mm_cmpge_ps(lhs._values, rhs._values));
			}

			//------------------------------------------------------------
			// Arithmetic operators
			//------------------------------------------------------------

			/**
			 * Negates the sse_type.
			 *
			 * \returns The negation of the type.
			 */
			inline const sse_type operator- () const
			{
				return sse_type(_mm_sub_ps(_mm_setzero_ps(), _values));
			}

			/**
			 * Adds two sse_types.
			 *
			 * \param rhs Source type.
			 * \returns Result of the addition.
			 */
			inline const sse_type operator+ (const sse_type& rhs) const
			{
				return sse_type(_mm_add_ps(_values, rhs._values));
			}

			/**
			 * Subtracts a sse_type from a sse_type.
			 *
			 * \param rhs Source type.
			 * \returns Result of the subtraction.
			 */
			inline const sse_type operator- (const sse_type& rhs) const
			{
				return sse_type(_mm_sub_ps(_values, rhs._values));
			}

			/**
			 * Multiplies a sse_type by a sse_type.
			 *
			 * \param rhs Source type.
			 * \returns Result of the multiplication.
			 */
			inline const sse_type operator* (const sse_type& rhs) const
			{
				return sse_type(_mm_mul_ps(_values, rhs._values));
			}

			/**
			 * Divides a sse_type by a sse_type.
			 *
			 * \param rhs Source type.
			 * \returns Result of the division.
			 */
			inline const sse_type operator/ (const sse_type& rhs) const
			{
				return sse_type(_mm_div_ps(_values, rhs._values));
			}

			/**
			 * Adds two sse_types.
			 *
			 * \param rhs Source type.
			 * \returns Result of the addition.
			 */
			inline sse_type& operator+= (const sse_type& rhs)
			{
				_values = _mm_add_ps(_values, rhs._values);

				return *this;
			}

			/**
			 * Subtracts a sse_type from a sse_type.
			 *
			 * \param rhs Source type.
			 * \returns Result of the subtraction.
			 */
			inline sse_type& operator-= (const sse_type& rhs)
			{
				_values = _mm_sub_ps(_values, rhs._values);

				return *this;
			}

			/**
			 * Multiplies a sse_type by a sse_type.
			 *
			 * \param rhs Source type.
			 * \returns Result of the multiplication.
			 */
			inline sse_type& operator*= (const sse_type& rhs)
			{
				_values = _mm_mul_ps(_values, rhs._values);

				return *this;
			}

			/**
			 * Divides a sse_type by a sse_type.
			 *
			 * \param rhs Source type.
			 * \returns Result of the division.
			 */
			inline sse_type& operator/= (const sse_type& rhs)
			{
				_values = _mm_div_ps(_values, rhs._values);

				return *this;
			}

			//------------------------------------------------------------
			// Arithmetic operations
			//------------------------------------------------------------

			/**
			 * Takes the inverse of an sse_type.
			 *
			 * \code
			 * results[0] = 1.0f / a[0];
			 * results[1] = 1.0f / a[1];
			 * results[2] = 1.0f / a[2];
			 * results[3] = 1.0f / a[3];
			 * \endcode
			 *
			 * \param a Source type.
			 * \returns The inverse of a sse_type.
			 */
			inline static const sse_type inv(const sse_type& a)
			{
				return sse_type(_mm_rcp_ps(a._values));
			}

			/**
			 * Takes the square root of an sse_type.
			 *
			 * \code
			 * results[0] = sqrt(a[0]);
			 * results[1] = sqrt(a[1]);
			 * results[2] = sqrt(a[2]);
			 * results[3] = sqrt(a[3]);
			 * \endcode
			 * 
			 * \param a Source type.
			 * \returns The sqrt of a sse_type.
			 */
			inline static const sse_type sqrt(const sse_type& a)
			{
			#ifdef BASIS_USE_FAST_SQRT
				return sse_type(_mm_mul_ps(a._values, _mm_rsqrt_ps(a._values)));
			#else
				// Perform a Newton-Raphson iteration on the reciprocal
				// yn+1 = (yn * (3 - xyn^2)) / 2
				const __m128 yn = _mm_rsqrt_ps(a._values);
				const __m128 xyn2 = _mm_mul_ps(_mm_mul_ps(a._values, yn), yn);

				return sse_type(
					_mm_mul_ps(_mm_mul_ps(_mm_set_ps1(0.5f), _mm_mul_ps(a._values, yn)), _mm_sub_ps(_mm_set_ps1(3.0f), xyn2)));
			#endif
			}

			/**
			 * Takes the inverse square root of an sse_type.
			 *
			 * \code
			 * results[0] = 1.0f / sqrt(a[0]);
			 * results[1] = 1.0f / sqrt(a[1]);
			 * results[2] = 1.0f / sqrt(a[2]);
			 * results[3] = 1.0f / sqrt(a[3]);
			 * \endcode
			 *
			 * \param a Source type.
			 * \returns The inverse sqrt of a sse_type.
			 */
			inline static const sse_type inv_sqrt(const sse_type& a)
			{
			#ifdef BASIS_USE_FAST_SQRT
				return sse_type(_mm_rsqrt_ps(a._values));
			#else
				// Perform a Newton-Raphson iteration on the reciprocal
				// yn+1 = (yn * (3 - xyn^2)) / 2
				const __m128 yn = _mm_rsqrt_ps(a._values);
				const __m128 xyn2 = _mm_mul_ps(_mm_mul_ps(a._values, yn), yn);

				return sse_type(
					_mm_mul_ps(_mm_mul_ps(_mm_set_ps1(0.5f), yn), _mm_sub_ps(_mm_set_ps1(3.0f), xyn2)));
			#endif
			}

			/**
			 * Takes the component-wise minimum of two sse_types.
			 *
			 * \code
			 * results[0] = (a[0] < b[0]) ? a[0] : b[0];
			 * results[1] = (a[1] < b[1]) ? a[1] : b[1];
			 * results[2] = (a[2] < b[2]) ? a[2] : b[2];
			 * results[3] = (a[3] < b[3]) ? a[3] : b[3];
			 * \endcode
			 *
			 * \param a Source type.
			 * \param b Source type.
			 * \returns The component-wise minimum of two sse_types.
			 */
			inline static const sse_type min(const sse_type& a, const sse_type& b)
			{
				return sse_type(_mm_min_ps(a._values, b._values));
			}

			/**
			 * Takes the component-wise maximum of two sse_types.
			 *
			 * \code
			 * results[0] = (a[0] > b[0]) ? a[0] : b[0];
			 * results[1] = (a[1] > b[1]) ? a[1] : b[1];
			 * results[2] = (a[2] > b[2]) ? a[2] : b[2];
			 * results[3] = (a[3] > b[3]) ? a[3] : b[3];
			 * \endcode
			 *
			 * \param a Source type.
			 * \param b Source type.
			 * \returns The component-wise maximum of two sse_types.
			 */
			inline static const sse_type max(const sse_type& a, const sse_type& b)
			{
				return sse_type(_mm_max_ps(a._values, b._values));
			}

			//------------------------------------------------------------
			// Horizontal operations
			//------------------------------------------------------------

			/**
			 * Performs a horizontal addition.
			 *
			 * A horizontal addition means that adjacent elements in the same operand are added
			 * together. The following code illustrates the operation.
			 *
			 * \code
			 * result[0] = a[0] + a[1];
			 * result[1] = a[2] + a[3];
			 * result[2] = b[0] + b[1];
			 * result[3] = b[2] + b[3];
			 * \endcode
			 *
			 * \param a The first operand.
			 * \param b The second operand.
			 */
			inline static const sse_type hadd(const sse_type& a, const sse_type& b)
			{
			#if BASIS_SSE_LEVEL >= 3
				return sse_type(_mm_hadd_ps(a._values, b._values));
			#else
				const __m128 t0 = _mm_shuffle_ps(a._values, b._values, _MM_SHUFFLE(2, 0, 2, 0));
				const __m128 t1 = _mm_shuffle_ps(a._values, b._values, _MM_SHUFFLE(3, 1, 3, 1));

				return sse_type(_mm_add_ps(t0, t1));
			#endif
			}

			/**
			 * Performs a horizontal subtraction.
			 *
			 * A horizontal subtraction means that adjacent elements in the same operand are added
			 * together. The following code illustrates the operation.
			 *
			 * \code
			 * result[0] = a[0] - a[1];
			 * result[1] = a[2] - a[3];
			 * result[2] = b[0] - b[1];
			 * result[3] = b[2] - b[3];
			 * \endcode
			 *
			 * \param a The first operand.
			 * \param b The second operand.
			 * \returns An sse_type containing the result of the horizontal operation.
			 */
			inline static const sse_type hsub(const sse_type& a, const sse_type& b)
			{
			#if BASIS_SSE_LEVEL >= 3
				return sse_type(_mm_hsub_ps(a._values, b._values));	
			#else
				const __m128 t0 = _mm_shuffle_ps(a._values, b._values, _MM_SHUFFLE(2, 0, 2, 0));
				const __m128 t1 = _mm_shuffle_ps(a._values, b._values, _MM_SHUFFLE(3, 1, 3, 1));

				return sse_type(_mm_sub_ps(t0, t1));
			#endif
			}

			//------------------------------------------------------------
			// Vector operations
			//------------------------------------------------------------

			/**
			 * Calculates the dot product of two sse_types.
			 *
			 * Calculates the dot product of a 2D vector.
			 *
			 * \param value1 Source vector.
			 * \param value2 Source vector.
			 * \returns The dot product of two sse_types.
			 */
			inline static const sse_type dot2(const sse_type& value1, const sse_type& value2)
			{
			#if BASIS_SSE_LEVEL >= 4
				return sse_type(_mm_dp_ps(value1._values, value2._values, 0x3F));
			#else
				const __m128 t0 = _mm_mul_ps(value1._values, value2._values);
				const __m128 t1 = _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(0,0,0,0));
				const __m128 t2 = _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(1,1,1,1));

				return sse_type(_mm_add_ps(t1, t2));
			#endif
			}

			/**
			 * Calculates the dot product of two sse_types.
			 *
			 * Calculates the dot product of a 3D vector.
			 *
			 * \param value1 Source vector.
			 * \param value2 Source vector.
			 * \returns The dot product of two sse_types.
			 */
			inline static const sse_type dot3(const sse_type& value1, const sse_type& value2)
			{
			#if BASIS_SSE_LEVEL >= 4
				return sse_type(_mm_dp_ps(value1._values, value2._values, 0x7F));
			#else
				const __m128 t0 = _mm_mul_ps(value1._values, value2._values);
				const __m128 t1 = _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(0,0,0,0));
				const __m128 t2 = _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(1,1,1,1));
				const __m128 t3 = _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(2,2,2,2));

				return sse_type(_mm_add_ps(t1, _mm_add_ps(t2, t3)));
			#endif
			}

			/**
			 * Calculates the dot product of two sse_types.
			 *
			 * Calculates the dot product of a 4D vector.
			 *
			 * \param value1 Source vector.
			 * \param value2 Source vector.
			 * \returns The dot product of two sse_types.
			 */
			inline static const sse_type dot4(const sse_type& value1, const sse_type& value2)
			{
			#if BASIS_SSE_LEVEL >= 4
				return sse_type(_mm_dp_ps(value1._values, value2._values, 0xFF));
			#elif BASIS_SSE_LEVEL >= 3
				const __m128 t0 = _mm_mul_ps(value1._values, value2._values);
				const __m128 t1 = _mm_hadd_ps(t0, t0);

				return sse_type(_mm_hadd_ps(t1, t1));
			#else
				const __m128 t0 = _mm_mul_ps(value1._values, value2._values);
				const __m128 t1 = _mm_shuffle_ps(t0, t0, _MM_SHUFFLE(1,0,3,2));
				const __m128 t2 = _mm_add_ps(t0, t1);
				const __m128 t3 = _mm_shuffle_ps(t2, t2, _MM_SHUFFLE(2,3,0,1));

				return sse_type(_mm_add_ps(t3, t2));
			#endif
			}

			//------------------------------------------------------------
			// Shuffle operations
			//------------------------------------------------------------

			/**
			 * Joins values from two sse_types.
			 *
			 * \code
			 * result[0] = a[_0];
			 * result[1] = a[_1];
			 * result[2] = b[_2];
			 * result[3] = b[_3];
			 * \endcode
			 *
			 * \tparam _0 The element from a to select for the 1st slot.
			 * \tparam _1 The element from a to select for the 2nd slot.
			 * \tparam _2 The element from b to select for the 3rd slot.
			 * \tparam _3 The element from b to select for the 4th slot.
			 *
			 * \param a Source type.
			 * \param b Source type.
			 * \returns The result of the shuffle operation.
			 */
			template <size_type _0, size_type _1, size_type _2, size_type _3>
			inline static const sse_type shuffle(const sse_type& a, const sse_type& b)
			{
				const unsigned int mask = (_3 << 6) | (_2 << 4) | (_1 << 2) | _0;

				return sse_type(_mm_shuffle_ps(a._values, b._values, mask));
			}

			/**
			 * Shuffles the location of elements in the sse_type.
			 *
			 * \code
			 * result[0] = a[_0];
			 * result[1] = a[_1];
			 * result[2] = a[_2];
			 * result[3] = a[_3];
			 * \endcode
			 *
			 * \tparam _0 The element to select for the 1st slot.
			 * \tparam _1 The element to select for the 2nd slot.
			 * \tparam _2 The element to select for the 3rd slot.
			 * \tparam _3 The element to select for the 4th slot.
			 *
			 * \returns The result of the shuffle operation.
			 */
			template <size_type _0, size_type _1, size_type _2, size_type _3>
			inline const sse_type shuffle() const
			{
				return shuffle<_0, _1, _2, _3>(_values, _values);
			}

		private:

			/// The 128-bit floating point SIMD type
			__m128 _values;

			/**
			 * Initializes an instance of the sse_type class.
			 *
			 * \param values The SIMD type to use.
			 */
			inline sse_type(const __m128 values)
				: _values(values)
			{ }

	} ; // end class sse_type<float>

} } // end namespace basis::detail

#endif // end #if (BASIS_SSE_LEVEL > 0)

#endif // end BASIS_DETAIL_SSE_TYPE_HPP_INCLUDED
